package ch.ethz.fcl.metrobuzz.tools;

import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.vecmath.Vector2d;
import javax.vecmath.Vector3f;

import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;

public class MBTools {
	public static final String SEPERATOR = "+";

	/**
	 * 
	 * copied from www.processing.org, PApplet.map
	 */
	static public final float map(float value, float istart, float istop,
			float ostart, float ostop) {
		return ostart + (ostop - ostart)
				* ((value - istart) / (istop - istart));
	}

	public static final float[] map(float value, float istart, float istop,
			float[] ostart, float[] ostop) {
		float[] output = new float[ostart.length];
		for (int i = 0; i < ostart.length; i++)
			output[i] = map(value, istart, istop, ostart[i], ostop[i]);
		return output;
	}

	public static void debug(String s, boolean b) {
		if (b) {
			System.out.println("DEBUG: " + s);
		}
	}

	public static Vector3f rotate(Vector3f v, Vector3f axis, float angle) {
		// Rotate the point (x,y,z) around the vector (u,v,w)
		// Function RotatePointAroundVector(x#,y#,z#,u#,v#,w#,a#)
		float ux = axis.x * v.x;
		float uy = axis.x * v.y;
		float uz = axis.x * v.z;
		float vx = axis.y * v.x;
		float vy = axis.y * v.y;
		float vz = axis.y * v.z;
		float wx = axis.z * v.x;
		float wy = axis.z * v.y;
		float wz = axis.z * v.z;
		float sa = (float) Math.sin(angle);
		float ca = (float) Math.cos(angle);
		float x = axis.x
				* (ux + vy + wz)
				+ (v.x * (axis.y * axis.y + axis.z * axis.z) - axis.x
						* (vy + wz)) * ca + (-wy + vz) * sa;
		float y = axis.y
				* (ux + vy + wz)
				+ (v.y * (axis.x * axis.x + axis.z * axis.z) - axis.y
						* (ux + wz)) * ca + (wx - uz) * sa;
		float z = axis.z
				* (ux + vy + wz)
				+ (v.z * (axis.x * axis.x + axis.y * axis.y) - axis.z
						* (ux + vy)) * ca + (-vx + uy) * sa;

		return new Vector3f(x, y, z);

	}

	public static int sum(int[] input) {
		int sum = 0;
		for (int i = 0; i < input.length; i++)
			sum += input[i];

		return sum;
	}

	public static float sum(float[] input) {
		float sum = 0;
		for (int i = 0; i < input.length; i++)
			sum += input[i];

		return sum;
	}

	public static void print(int[] in, boolean print) {
		if (!print)
			return;
		int sum = 0;
		for (int i = 0; i < in.length; i++) {
			System.out.print(in[i] + " ");
			sum += in[i];
		}
		System.out.println("sum " + sum);
		System.out.println();
	}

	public static void print(float[] in, boolean print) {
		if (!print)
			return;
		float sum = 0;
		for (int i = 0; i < in.length; i++) {
			System.out.print(in[i] + " ");
			sum += in[i];
		}
		System.out.println("sum " + sum);
		System.out.println();
	}

	public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
		for (Entry<T, E> entry : map.entrySet()) {
			if (value.equals(entry.getValue())) {
				return entry.getKey();
			}
		}
		return null;
	}

	public static float log_ceiling(int input) {
		int two_pow = 0;
		float i = 0;
		if (input < 1)
			return 0;
		while (two_pow < input) {
			two_pow = (int) Math.pow(2, i);
			i++;
		}
		return i;
	}

	public static double cos(Vector2d v1, Vector2d v2) {
		return Math.cos(angle(v1, v2));
	}

	/**
	 * angle from v1 to v2 anti clockwise: [0, PI) clockwise: [-PI, 0)
	 * 
	 * @param v1
	 * @param v2
	 * @return
	 */
	public static double angle(Vector2d v1, Vector2d v2) {
		double angle1 = Math.atan2(v1.y, v1.x);
		double angle2 = Math.atan2(v2.y, v2.x);
		double angle = angle2 - angle1;

		if (angle < -Math.PI)
			angle = 2 * Math.PI + angle;
		else if (angle > Math.PI)
			angle = angle - 2 * Math.PI;
		else if (angle == Math.PI)
			angle = -Math.PI;
		return angle;
	}

	/**
	 * 
	 * @param vec
	 * @param angle
	 *            angle in radians, minus is clockwise,
	 * @return
	 */
	public static Vector2d rotate(Vector2d vec, double angle) {
		double rx = (vec.x * Math.cos(angle)) - (vec.y * Math.sin(angle));
		double ry = (vec.x * Math.sin(angle)) + (vec.y * Math.cos(angle));
		return new Vector2d(rx, ry);
	}

	public static Point2D rotate(Point2D p, Point2D origin, double angle) {
		float x = (float) (p.getX() * Math.cos(angle) - p.getY()
				* Math.sin(angle) - origin.getX() * Math.cos(angle)
				+ origin.getY() * Math.sin(angle) + origin.getX());
		float y = (float) (p.getX() * Math.sin(angle) + p.getY()
				* Math.cos(angle) - origin.getX() * Math.sin(angle)
				- origin.getY() * Math.cos(angle) + origin.getY());
		return new Point2D(x, y);
	}

	public static float distance(float x1, float y1, float x2, float y2) {
		return (float) Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
	}

	// return minimum distance between a Point p and a line segement
	// ref: http://geomalgorithms.com/a02-_lines.html
	public static float distance(Point2D p, Point2D lineP0, Point2D lineP1) {
		Vector2d v = new Vector2d(lineP1.getX() - lineP0.getX(), lineP1.getY()
				- lineP0.getY());
		Vector2d w = new Vector2d(p.getX() - lineP0.getX(), p.getY()
				- lineP0.getY());

		double c1 = w.dot(v);
		if (c1 <= 0)
			return p.distance(lineP0);
		double c2 = v.dot(v);

		if (c2 <= c1)
			return p.distance(lineP1);

		double b = c1 / c2;
		Point2D pb = lineP0.getPointAt(lineP1, (float) b);

		return p.distance(pb);
	}

	/**
	 * 
	 * @param time
	 *            in secs:mins:hours format
	 * @return time normalized between [0, 24*60*60]
	 */
	public static float normalizedTime(String time) {
		return convertTimeFromString(time) / (24 * 60 * 60.0f);
	}

	/**
	 * 
	 * @param time
	 *            in secs:mins:hours format
	 * @return converted time integer
	 */
	public static float convertTimeFromString(String time) {
		if (time == null)
			return 0;
		int h = Integer.parseInt(time.substring(0, 2));
		int m = Integer.parseInt(time.substring(3, 5));
		int s = Integer.parseInt(time.substring(6, 8));
		int timeInSec = 3600 * h + 60 * m + s;
		if (timeInSec > 3600 * 24)
			timeInSec -= 3600 * 24;
		return timeInSec;
	}

	public static int closestInteger(double in) {
		double diff = in - (int) in;
		if (diff > 0.5)
			return (int) in + 1;
		else
			return (int) in;
	}

	/**
	 * 
	 * @param time
	 *            in seconds
	 * @return formated string of time
	 */
	public static String formatTimeInSeconds(float time) {
		DecimalFormat formater = new DecimalFormat("00");

		if (time < 0)
			time += 3600 * 24;
		int hour = (int) (time / 3600);
		int min = (int) (time - hour * 3600) / 60;
		float sec = time - hour * 3600 - min * 60;

		return formater.format(hour) + ":" + formater.format(min) + ":"
				+ formater.format(sec);
	}

	// check if input string is numeric
	public static boolean isNumeric(String str) {
		// remove first 0s
		str = str.replaceFirst("^0+(?!$)", "");
		try {
			Double.parseDouble(str);
		} catch (NumberFormatException nfe) {
			return false;
		}
		return true;
	}

	public static String numToPercentage(float number) {
		return MessageFormat.format("{0,number,#.##%}", number);
	}

	public static String formatNum(int value) {
		DecimalFormat myFormatter = new DecimalFormat("###,###");
		String output = myFormatter.format(value);

		return output;
	}

	public static String formatNum(String format, float value) {
		DecimalFormat myFormatter = new DecimalFormat(format);
		String output = myFormatter.format(value);

		return output;
	}

	public static <T> Map<T, ? extends Number> sortByComparator(
			Map<T, ? extends Number> unsortMap, final boolean order) {
		List<Entry<T, ? extends Number>> list = new LinkedList<Entry<T, ? extends Number>>(
				unsortMap.entrySet());

		// Sorting the list based on values
		Collections.sort(list, new Comparator<Entry<T, ? extends Number>>() {
			public int compare(Entry<T, ? extends Number> o1,
					Entry<T, ? extends Number> o2) {
				if (o1.getValue() instanceof Float) {
					if (order) {
						return ((Float) o1.getValue()).compareTo((Float) o2
								.getValue());
					} else
						return ((Float) o2.getValue()).compareTo((Float) o1
								.getValue());
				} else if (o1.getValue() instanceof Integer) {
					if (order) {
						return ((Integer) o1.getValue()).compareTo((Integer) o2
								.getValue());
					} else
						return ((Integer) o2.getValue()).compareTo((Integer) o1
								.getValue());
				} else {
					if (order) {
						return ((Double) o1.getValue()).compareTo((Double) o2
								.getValue());
					} else {
						return ((Double) o2.getValue()).compareTo((Double) o1
								.getValue());

					}
				}
			}
		});

		// Maintaining insertion order with the help of LinkedList
		Map<T, Number> sortedMap = new LinkedHashMap<T, Number>();
		for (Entry<T, ? extends Number> entry : list) {
			sortedMap.put(entry.getKey(), entry.getValue());
		}

		return sortedMap;
	}

	public static <T> int[] getIndices(Map<T, Integer> countMap,
			Map<String, Integer> indexMap, T start, String startId, T end,
			String endId) {
		int[] indices = { -1, -1 };

		Integer startCount = countMap.get(start);
		Integer endCount = countMap.get(end);

		if (startCount != null && endCount != null) {
			if (startCount == 1 && endCount == 1) {
				indices[0] = indexMap.get(startId);
				indices[1] = indexMap.get(endId);
			} else {
				int[] startIndices = new int[startCount];
				for (int i = 0; i < startCount; i++) {
					if (i == 0)
						startIndices[i] = indexMap.get(startId);
					else
						startIndices[i] = indexMap.get(startId + SEPERATOR + i);
				}

				int[] endIndices = new int[endCount];
				for (int i = 0; i < endCount; i++) {
					if (i == 0)
						endIndices[i] = indexMap.get(endId);
					else
						endIndices[i] = indexMap.get(endId + SEPERATOR + i);
				}

				for (int i = 0; i < endIndices.length; i++) {
					indices[1] = endIndices[i];
					boolean success = false;
					for (int j = startIndices.length - 1; j >= 0; j--) {
						indices[0] = startIndices[j];
						if (indices[0] < indices[1]) {
							success = true;
							break;
						}
					}
					if (success)
						break;
				}
			}
		}

		return indices;
	}
}
